#include "GFrustum.h"
#include "GMath.h"
#include "GVector2.h"
#include "GVector3.h"

namespace GMath
{
	GFrustum::GFrustum()
	{
		AspectRatio = 16.0f / 10.0f;
		FieldOfView = 45.0f;
		NearZ = 0.2f;
		FarZ = 100.0f;

		Right = GVector3::UnitX;
		Up = GVector3::UnitY;
		Front = GVector3::UnitZ;
	}

	GFrustum::GFrustum(float TheAspectRatio, float TheFieldOfView, float TheNearZ, float TheFarZ, const GVector3& ThePosition, const GQuaternion& Orientation)
	{
		Create(TheAspectRatio, TheFieldOfView, TheNearZ, TheFarZ, ThePosition, Orientation);
	}

	GFrustum::GFrustum(float TheAspectRatio, float TheFieldOfView, float TheNearZ, float TheFarZ, const GVector3& ThePosition, const GVector3& TheRight, const GVector3& TheUp, const GVector3& TheFront)
	{
		Create(TheAspectRatio, TheFieldOfView, TheNearZ, TheFarZ, ThePosition, TheRight, TheUp, TheFront);
	}

	GFrustum::~GFrustum()
	{
	}

	void GFrustum::Create(float TheAspectRatio, float TheFieldOfView, float TheNearZ, float TheFarZ, const GVector3& ThePosition, const GQuaternion& Orientation)
	{
		AspectRatio = TheAspectRatio;
		FieldOfView = TheFieldOfView;
		NearZ = TheNearZ;
		FarZ = TheFarZ;

		Position = ThePosition;

		GQuaternion::Multiply(Right, GVector3::UnitX, Orientation);
		GQuaternion::Multiply(Up, GVector3::UnitY, Orientation);
		GQuaternion::Multiply(Front, GVector3::UnitZ, Orientation);
	}

	void GFrustum::Create(float TheAspectRatio, float TheFieldOfView, float TheNearZ, float TheFarZ, const GVector3& ThePosition, const GVector3& TheRight, const GVector3& TheUp, const GVector3& TheFront)
	{
		AspectRatio = TheAspectRatio;
		FieldOfView = TheFieldOfView;
		NearZ = TheNearZ;
		FarZ = TheFarZ;

		Position = ThePosition;

		Right = TheRight;
		Up = TheUp;
		Front = TheFront;
	}

	const GVector3& GFrustum::GetRight() const
	{
		return Right;
	}

	const GVector3& GFrustum::GetUp() const
	{
		return Up;
	}

	const GVector3& GFrustum::GetFront() const
	{
		return Front;
	}

	void GFrustum::GetPlanes(GPlane PlaneArray[6]) const
	{
		float TanFovOver2 = TanF(FieldOfView / 2.0f);

		float NearHalfHeight = TanFovOver2 * NearZ;
		float NearHalfWidth = NearHalfHeight * AspectRatio;

		GVector3 FarPlaneCenter = Position + Front * FarZ;
		GVector3 NearPlaneCenter = Position + Front * NearZ;

		
		GVector3 NormalRight;
		GVector3 Temp0 = (NearPlaneCenter + Right * NearHalfWidth) - Position;
		GVector3::Normalize(Temp0, Temp0);
		GVector3::Cross(NormalRight, Up, Temp0);

		GVector3 NormalBottom;
		GVector3 Temp1 = (NearPlaneCenter - Up * NearHalfHeight) - Position;
		GVector3::Normalize(Temp1, Temp1);
		GVector3::Cross(NormalRight, Right, Temp1);

		GVector3 NormalLeft;
		GVector3 Temp2 = (NearPlaneCenter - Right * NearHalfWidth) - Position;
		GVector3::Normalize(Temp2, Temp2);
		GVector3::Cross(NormalRight, Temp2, Up);
		
		GVector3 NormalTop;
		GVector3 Temp3 = (NearPlaneCenter + Up * NearHalfHeight) - Position;
		GVector3::Normalize(Temp3, Temp3);
		GVector3::Cross(NormalRight, Temp3, Right);
	
		PlaneArray[G_BP_RIGHT]	= GPlane(Position, NormalRight);
		PlaneArray[G_BP_BOTTOM] = GPlane(Position, NormalBottom);
		PlaneArray[G_BP_LEFT]	= GPlane(Position, NormalLeft);
		PlaneArray[G_BP_TOP]	= GPlane(Position, NormalTop);
		PlaneArray[G_BP_NEAR]	= GPlane(NearPlaneCenter, Front);
		PlaneArray[G_BP_FAR]	= GPlane(FarPlaneCenter, -Front);
	}

	void GFrustum::GetCorners(GVector3 CornerArray[8]) const
	{
		float TanFovOver2 = TanF(FieldOfView / 2.0f);

		float NearHalfHeight = TanFovOver2 * NearZ;
		float NearHalfWidth = NearHalfHeight * AspectRatio;
		
		float FarHalfHeight = TanFovOver2 * FarZ;
		float FarHalfWidth = FarHalfHeight * AspectRatio;

		GVector3 FarPlaneCenter = Position + Front * FarZ;
		GVector3 NearPlaneCenter = Position + Front * NearZ;

		CornerArray[G_BC_NEAR_BOTTOM_LEFT]	= NearPlaneCenter - Up * NearHalfHeight - Right * NearHalfWidth;
		CornerArray[G_BC_NEAR_BOTTOM_RIGHT]	= NearPlaneCenter - Up * NearHalfHeight + Right * NearHalfWidth;
		CornerArray[G_BC_NEAR_TOP_LEFT]		= NearPlaneCenter + Up * NearHalfHeight - Right * NearHalfWidth;
		CornerArray[G_BC_NEAR_TOP_RIGHT]	= NearPlaneCenter + Up * NearHalfHeight + Right * NearHalfWidth;
		CornerArray[G_BC_FAR_BOTTOM_LEFT]	= FarPlaneCenter - Up * FarHalfHeight - Right * FarHalfWidth;
		CornerArray[G_BC_FAR_BOTTOM_RIGHT]	= FarPlaneCenter - Up * FarHalfHeight + Right * FarHalfWidth;
		CornerArray[G_BC_FAR_TOP_LEFT]		= FarPlaneCenter + Up * FarHalfHeight - Right * FarHalfWidth;
		CornerArray[G_BC_FAR_TOP_RIGHT]		= FarPlaneCenter + Up * FarHalfHeight + Right * FarHalfWidth;
	}

	float GFrustum::GetArea() const
	{
		return 0.0f;
	}

	float GFrustum::GetVolume() const
	{
		// A = near z area
		// A' = far z area
		// Volume = (h / 3.0f) * (A + A' + sqrtf(A * A'))
		float TanFovOver2 = GMath::TanF(FieldOfView / 2.0f);

		float NearHalfHeight = TanFovOver2 * NearZ;
		float NearHalfWidth = NearHalfHeight * AspectRatio;
		
		float FarHalfHeight = TanFovOver2 * FarZ;
		float FarHalfWidth = FarHalfHeight * AspectRatio;

		float FarArea =  FarHalfHeight * 2.0f * FarHalfWidth * 2.0f;
		float NearArea =  NearHalfHeight * 2.0f * NearHalfWidth * 2.0f;

		float Temp = SqrtF(FarArea * NearArea);
		return (FarZ - NearZ) / 3.0f * (NearArea + FarArea + Temp);
	}

}